#define _CRT_SECURE_NO_WARNINGS
#include <cstdio>
#include <vector>
#include <unordered_set>
#include <unordered_map>
#include <queue>
#include <algorithm>

constexpr int MAXM = 1010;
bool visited[MAXM][MAXM];

/**
 * 车站
 */
int main() {
	int n, m;
	scanf("%d%d", &n, &m);

	std::vector<std::unordered_set<int>> graph(n + 1);
	std::vector<int> indegree(n + 1, 0);

	for (int i = 0; i < m; ++i) {
		int s;
		scanf("%d", &s);
		std::vector<int> stopped(s);
		std::unordered_set<int> stopSet;

		int minStop = n + 1, maxStop = 0;
		for (int j = 0; j < s; ++j) {
			scanf("%d", &stopped[j]);
			stopSet.insert(stopped[j]);
			minStop = std::min(minStop, stopped[j]);
			maxStop = std::max(maxStop, stopped[j]);
		}

		// 优化后的非停靠站查找(使用set)
		for (int u = minStop; u <= maxStop; u++) {
			//非停靠站（利用set O(1)的查找性能）
			if (stopSet.find(u) == stopSet.end()) {
				for (int v : stopped) {
                    //必须使用数组而不是set来判断是否链接过，否则无法ac
					if (!visited[u][v]) {
						graph[u].insert(v);
						indegree[v]++;
						visited[u][v] = true;
					}
				}
			}
		}
	}

		// 拓扑排序 + DP
		std::queue<int> q;
		std::vector<int> dp(n + 1, 1);

		for (int i = 1; i <= n; ++i) {
			if (indegree[i] == 0) {
				q.push(i);
			}
		}

		while (!q.empty()) {
			int u = q.front();
			q.pop();
			for (int v : graph[u]) {
				dp[v] = std::max(dp[v], dp[u] + 1);
				if (--indegree[v] == 0) {
					q.push(v);
				}
			}
		}

		const int result = *std::max_element(dp.begin(), dp.end());
		printf("%d", result);

		return 0;
	}
